// encoding: UTF-8
/*
*
* Distributed under the terms of the GNU General Public License,
* see License.txt for details.
*
* Author: Dave Dyer  ddyer@real-me.net  Oct/2005
*
* bug fix 5/2013 by Nayuki Minase <nayuki@eigenstate.org>
*   encrypted file was corrupted for source files of 16-31 bytes.
*   also http://nayuki.eigenstate.org/page/notepadcrypt-format-decryptor-java
*/
#include "targetver.h"
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

#include "rijndael-api-fst.h"
#include "crypto.h"

#define BLOCKSIZE  (64 * 1024) // the optimal buffer size for sequential I/O on Windows NT/2k/XP
typedef struct AES_file
{
    FILE *file;
    AES_cipherInstance cipher;
    AES_keyInstance key;
    BOOL encrypted;
    BYTE buffer[BLOCKSIZE];
    long bytesleft;
    long buffer_index;
    long buffer_end;
} AES_file;


void gen_iv(unsigned char *buf, int size)
{
    for (int i = 0; i < size; i++) {
        buf[i] = ((i % 2) == 0) ? HIBYTE(rand()) : LOBYTE(rand());
    }
}

/* @func
open a file, possibly encrypted using notepad2 format, for reading and decryption.
@rdesc 0 for success
*/
long ROpen_AES
(char * name,	//@parm the file to open
 AES_file * fp, //@parm the <t AES_file> object to keep track of the open file
 char *filekey, //@parm the file's passphrase, or an empty string, or NULL
 char *masterkey//@parm the file's master passphrase, or  an empty string, or NULL
)
{
    FILE *file = NULL;
    if (fopen_s(&file, name, "rb") != 0)
    {
        printf("File %s can't be opened\n", name);
        return(1);
    }
    fp->file = file;
    fp->buffer_index = 0;
    fp->buffer_end = 0;
    fp->bytesleft = 0;
    fp->encrypted = FALSE;
    // get the file length
    fseek(file, 0, SEEK_END);
    fp->bytesleft = ftell(file);
    fseek(file, 0, SEEK_SET);

    // read the maximum preable size, so we will have an even number of encrypted blocks
    // left over if this is an encrypted file.
    fp->buffer_end = (long)fread(fp->buffer, 1, MASTER_KEY_OFFSET, fp->file);
    fp->bytesleft -= fp->buffer_end;
    if (fp->buffer_end >= MASTER_KEY_OFFSET)
    {
        unsigned long *lbuf = (unsigned long *)&fp->buffer;
        BYTE binFileKey[KEY_BYTES];
        BOOL hasFileKey = FALSE;

        //possibly encrypted
        if (lbuf[0] == PREAMBLE)
        {
            switch (lbuf[1])
            {
            default:
                printf("File %s is encrypted with an unsupported format: %lu", name, lbuf[1]);
                fclose(file);
                return(1);
            case MASTERKEY_FORMAT:
                // read the masterkey block
                if (fread(fp->buffer + fp->buffer_end, 1, KEY_BYTES + AES_MAX_IV_SIZE, fp->file)
                        != (KEY_BYTES + AES_MAX_IV_SIZE))
                {
                    fclose(fp->file);
                    return(2);	// short file
                }
                fp->buffer_index = fp->buffer_end;
                fp->bytesleft -= (KEY_BYTES + AES_MAX_IV_SIZE);

                if (masterkey && *masterkey)
                {
                    BYTE binMasterKey[KEY_BYTES];
                    AES_keygen(masterkey, binMasterKey);
                    AES_bin_setup(&fp->key, AES_DIR_DECRYPT, KEY_BYTES * 8, binMasterKey);
                    AES_bin_cipherInit(&fp->cipher, AES_MODE_CBC, &fp->buffer[MASTER_KEY_OFFSET]);
                    AES_blockDecrypt(&fp->cipher, &fp->key, &fp->buffer[MASTER_KEY_OFFSET + AES_MAX_IV_SIZE], sizeof(binFileKey), binFileKey);
                    hasFileKey = TRUE;
                }
                else if (filekey && *filekey)
                {
                    AES_keygen(filekey, binFileKey);
                    fp->buffer_index = fp->buffer_end;
                    hasFileKey = TRUE;
                }
                break;
            case FILEKEY_FORMAT:
                if (filekey && *filekey)
                {
                    AES_keygen(filekey, binFileKey);
                    fp->buffer_index = fp->buffer_end;
                    hasFileKey = TRUE;
                }
                break;
            }
            if (hasFileKey)
            {
                fp->encrypted = TRUE;
                AES_bin_setup(&fp->key, AES_DIR_DECRYPT, KEY_BYTES * 8, binFileKey);
                AES_bin_cipherInit(&fp->cipher, AES_MODE_CBC, &fp->buffer[PREAMBLE_SIZE]);
                return(0);
            }
            printf("File %s is encrypted, but no suitable passphrase is available",
                   name);
            fclose(file);
            return(3);
        }
    }
    return(0);	// file is too short to be encrypted
}
/* @func
 encrypt infile to outfile, using filephrase to generate the key,
 and optionally using masterphrase as the master key
*/
int encrypt(char *infile, char *outfile, char *filephrase, char *masterphrase)
{
    int err = 0;
    FILE *in = NULL;
    if (fopen_s(&in, infile, "rb") != 0)
    {
        printf("input file %s can't be opened\1", infile);
        err++;
    }
    else
    {
        FILE *out = NULL;
        if (fopen_s(&out, outfile, "wb") != 0)
        {
            printf("output file %s can't be opened\n", outfile);
            err++;
        }
        else
        {
            BYTE buffer[BLOCKSIZE];
            unsigned long preamble[] = { PREAMBLE, FILEKEY_FORMAT };
            BYTE iv[AES_MAX_IV_SIZE];
            BYTE filekey[KEY_BYTES];
            BOOL masterformat = masterphrase && *masterphrase;
            AES_cipherInstance cipher;
            AES_keyInstance key;

            if (masterformat)
            {
                preamble[1] = MASTERKEY_FORMAT;
            }

            gen_iv(iv, sizeof(iv));				    // generate a random iv
            AES_keygen(filephrase, filekey);		// make key file passphrase
            fwrite(preamble, 1, sizeof(preamble), out);	// write the preamble
            fwrite(iv, 1, sizeof(iv), out);				// and the iv

            AES_bin_setup(&key, AES_DIR_ENCRYPT, KEY_BYTES * 8, filekey);	// prepare the encryption
            AES_bin_cipherInit(&cipher, AES_MODE_CBC, iv);

            if (masterformat)  	// encrypt the file key with the masterkey and write it.
            {
                BYTE masteriv[AES_MAX_IV_SIZE];
                BYTE masterkey[KEY_BYTES];
                BYTE encfilekey[KEY_BYTES];
                AES_cipherInstance mastercipher;
                AES_keyInstance mkey;

                AES_keygen(masterphrase, masterkey);		// generate the master key
                gen_iv(masteriv, sizeof(masteriv));			// and an iv for it
                AES_bin_setup(&mkey, AES_DIR_ENCRYPT, KEY_BYTES * 8, masterkey);
                AES_bin_cipherInit(&mastercipher, AES_MODE_CBC, masteriv);
                // encrypt the file key using the master key
                AES_blockEncrypt(&mastercipher, &mkey, filekey, sizeof(filekey), encfilekey);
                fwrite(masteriv, 1, sizeof(masteriv), out);
                fwrite(encfilekey, 1, sizeof(encfilekey), out);
            }

            // now encrypt and output the actual data
            {
                ptrdiff_t bytesread = 0;
                ptrdiff_t bytesencrypted = 0;
                do
                {
                    bytesread = (long)fread(buffer, 1, sizeof(buffer), in);
                    bytesencrypted = 0;
                    if (bytesread > 0)
                    {
                        bytesencrypted = AES_blockEncrypt(&cipher, &key, buffer, bytesread, buffer);
                        fwrite(buffer, 1, bytesencrypted, out);
                    }
                }
                while ((bytesread > 0) && (bytesencrypted == bytesread));
                // pad the last block
                bytesencrypted = AES_padEncrypt(&cipher, &key, buffer + bytesencrypted, (bytesread - bytesencrypted), buffer);
                fwrite(buffer, 1, bytesencrypted, out);
                fclose(out);
            }
        }
        fclose(in);
    }
    return(err);
}
/* @func
 decrypt a file using filephrase or masterphrase.  If the file has a master key
 and masterphrase is supplied, masterphrase is used.  Otherwise filephrase.
 */
int decrypt(char *infile, char *outfile, char *filephrase, char *masterphrase)
{
    AES_file in;
    int err = 0;
    if (0 == ROpen_AES(infile, &in, filephrase, masterphrase))
    {
        FILE *out = NULL;
        if (fopen_s(&out, outfile, "wb") == 0)
        {
            while (in.bytesleft > 0)
            {
                if (in.buffer_index < in.buffer_end)   //write the data already available
                {
                    fwrite(in.buffer + in.buffer_index, 1, in.buffer_end - in.buffer_index, out);
                }
                // read and decrypt some more data
                {
                    long sizeread = (long)fread(in.buffer, 1, sizeof(in.buffer), in.file);
                    if (sizeread <= 0)
                    {
                        printf("ran out of input data\n");
                        in.bytesleft = 0;
                        err++;
                    }
                    AES_blockDecrypt(&in.cipher, &in.key, in.buffer, sizeread, in.buffer);
                    in.bytesleft -= sizeread;
                    in.buffer_index = 0;
                    in.buffer_end = sizeread;
                }
            }
            // now we just have one buffer containing some padding
            in.buffer_end -= in.buffer[in.buffer_end - 1];
            fwrite(in.buffer + in.buffer_index, 1, in.buffer_end - in.buffer_index, out);
            fclose(out);

        }
        fclose(in.file);
    }
    return(err);
}

int main(int argc, char *argv[])
{
    int err = 0;
    if (argc >= 4)
    {
        long idx = 1;
        char *op = argv[idx++];
        char *infile = argv[idx++];
        char *outfile = argv[idx++];
        char *pass1 = argv[idx++];
        char *pass2 = (idx < argc) ? argv[idx++] : "";

        srand((unsigned int)time(NULL)); // init pseudo random generator

        if (_stricmp(op, "EF") == 0)  	// encrypt with file passphrase only
        {
            encrypt(infile, outfile, pass1, "");
        }
        else if (_stricmp(op, "DF") == 0)  	// decrypt using the file passphrase
        {
            decrypt(infile, outfile, pass1, "");
        }
        else if ((_stricmp(op, "EM") == 0) && (*pass2 != (char)0))  	// encrypt using file and master passphrases
        {
            encrypt(infile, outfile, pass1, pass2);
        }
        else if (_stricmp(op, "DM") == 0)  	// decrypt using the master passphrase
        {
            decrypt(infile, outfile, "", pass1);
        }
        else
        {
            err++;
        }
    }
    else
    {
        err++;
    }
    if (err)
    {
        printf("\n%s - command line file encrypt/decrypt compatible with Notepad3\n\n"
               "Usage: %s {ef em df dm} source destination {passphrase} {passphrase}\n", argv[0], argv[0]);
        printf("       ef - encrypt w/filekey\n       em - encrypt w/masterkey\n"
               "       df - decrypt w/filekey\n       dm - decrypt w/masterkey\n\n");
    }

    return err;
}

